package com.siteseat.crudeq.broker.transport.impl;

import java.io.IOException;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import com.siteseat.common.foundation.PropertyHelper;
import com.siteseat.common.security.Credential;
import com.siteseat.common.security.CryptoHelper;
import com.siteseat.common.security.Permission;
import com.siteseat.common.security.SecurityException;
import com.siteseat.common.security.SecurityManager;
import com.siteseat.crudeq.broker.transport.BrokerSession;
import com.siteseat.crudeq.broker.transport.QueueNotExistsException;
import com.siteseat.crudeq.kernel.Message;
import com.siteseat.crudeq.kernel.MessageHistory;
import com.siteseat.crudeq.kernel.MessageId;
import com.siteseat.crudeq.kernel.Queue;
import com.siteseat.crudeq.kernel.QueueListener;
import com.siteseat.crudeq.kernel.QueueListenerManager;
import com.siteseat.crudeq.kernel.QueueManager;
import com.siteseat.crudeq.kernel.exception.InvalidChunkException;

/**
 *
 */
public abstract class BaseBrokeSession implements BrokerSession, QueueListener {

  private Locale                locale;

  private short                 version;

  private SecurityManager       securityManager;

  private String                clientId;

  private AtomicReference<Credential> user                   = new AtomicReference<Credential>();
  
  private QueueManager          queueManager;

  private AtomicLong            lastAccessTimeByMilliseconds = new AtomicLong( 0 );
  
  private volatile boolean waitingMessage;

  /**
   * @param internalId
   */
  public BaseBrokeSession( SecurityManager securityManager, QueueManager queueManager ) {

    this.securityManager = securityManager;
    PropertyHelper.ensureInitialized( null, securityManager, "securityManager" );

    this.queueManager = queueManager;
    PropertyHelper.ensureInitialized( null, queueManager, "queueManager" );

    updateAccessTime();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void ensureGranted( String resourceName, Permission permission ) throws SecurityException {
    String actor = getUserName();
    getSecurityManager().ensureGranted( actor, resourceName, permission );
  }
  
  /**
   * {@inheritDoc}
   */
  @Override
  public long getLastAccessTimeByMilliseconds() {
    return lastAccessTimeByMilliseconds.get();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void updateAccessTime() {
    this.lastAccessTimeByMilliseconds.set( System.currentTimeMillis() );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public short getVersion() {
    return this.version;
  }

  /**
   * {@inheritDoc}
   * @throws SecurityException 
   */
  @Override
  public byte[] encrypt( byte[] input ) throws SecurityException {
    return CryptoHelper.aesEncrypt( input, this.user.get().getEncryptionKey() );
  }

  /**
   * {@inheritDoc}
   * @throws SecurityException 
   */
  @Override
  public byte[] decrypt( byte[] input ) throws SecurityException {
    return CryptoHelper.aesDecrypt( input, this.user.get().getEncryptionKey() );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean login( String userName_, String password_, String clientId_, short version_, Locale locale_ )
          throws InterruptedException {

    Credential u = securityManager.authenticate( userName_, password_, clientId_ );
    if( u != null ) {
      this.user.set( u );

      this.clientId = clientId_;
      this.version = version_;
      this.locale = locale_;

      return true;
    }
    return false;
  }

  /**
   * Gets the authenticator
   * 
   * @return the authenticator
   */
  public SecurityManager getSecurityManager() {
    return securityManager;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isAuthenticated() {
    return this.user.get() != null;
  }

  /**
   * Gets the userName
   * 
   * @return the userName
   */
  public String getUserName() {
    return this.user.get().getName();
  }

  /**
   * Gets the clientId
   * 
   * @return the clientId
   */
  public String getClientId() {
    return clientId;
  }

  /**
   * @param qid
   * @return
   * @throws QueueNotExistsException
   */
  public Queue getQueue( Integer qid ) throws QueueNotExistsException {
    Queue q = this.queueManager.find( qid );
    if( q == null ) {
      throw new QueueNotExistsException( qid );
    }
    return q;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public MessageHistory fetchMessage( MessageId mid ) throws InvalidChunkException, QueueNotExistsException {
    Integer qid = mid.getQueueId();
    Queue q = getQueue( qid );
    return q.history( mid );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Message prepareMessage( Integer queueId, int messageSize ) throws QueueNotExistsException,
          InterruptedException, IOException {
    Queue q = getQueue( queueId );
    return q.prepare( messageSize );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void commitMessage( Message msg ) throws QueueNotExistsException {
    Queue q = getQueue( msg.getChunk().getInfo().getQueueId() );
    q.commit( msg );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void rollbackMessage( Message msg ) throws QueueNotExistsException {
    Queue q = getQueue( msg.getChunk().getInfo().getQueueId() );
    q.rollback( msg );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Locale getLocale() {
    return locale;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void subscribe( Set<Integer> queueIds ) throws InterruptedException {
    getQueueListnerManager().subscribe( queueIds, this );
  }
  

  /**
   * {@inheritDoc}
   */
  @Override
  public void unsubscribe( Set<Integer> queueIds ) throws InterruptedException {
    getQueueListnerManager().unsubscribe( queueIds, this );
  }
  
  /**
   * 
   *
   * @return
   */
  public QueueListenerManager getQueueListnerManager() {
    return this.queueManager.getListenerManager();
  }
  

  /**
   * {@inheritDoc}
   */
  @Override
  public void onMessageCommited( Message msg ) {
    if( this.waitingMessage && isConnected() ) {
      sendMessage( msg );
    }
  }
  
  /**
   * {@inheritDoc}
   */
  @Override
  public void startWaitMessage() {
    this.waitingMessage = true;
  }
  
  /**
   * {@inheritDoc}
   */
  @Override
  public void stopWaitMessage() {
    this.waitingMessage = false;
  }
  
  /**
   * 
   *
   * @param msg
   */
  protected abstract void sendMessage( Message msg ) ;

}
